<!doctype html>
<html>

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">

  <title>mm</title>

  <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/hakimel/reveal.js@4.1.2/dist/reset.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/hakimel/reveal.js@4.1.2/dist/reveal.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/hakimel/reveal.js@4.1.2/dist/theme/black.css">
  <style>
    .reveal .code-wrapper code {
      font-family: "Lucida Console", Courier, monospace;
    }
    .speaker-controls-notes .value {
      font-size: 1em;
    }

  </style>

  <!-- Theme used for syntax highlighted code -->
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/hakimel/reveal.js@4.1.2/plugin/highlight/monokai.css">
</head>

<body>
  <div class="reveal">
    <div class="slides">
      <section data-markdown>
        <textarea data-template>
          # 前端与接口
          本文主要用来分享前端开发中接口相关问题的解决方案。


          <aside class="notes" data-markdown>
            - 接口模拟, 数据模拟, 跨域, 接口联调, 项目实战
          </aside>
          ---

          ## 常见问题
          前端开发中关于接口和数据的常见问题。
          ![快看 那个学前端的上吊了](http://img.doutula.com/production/uploads/image/2018/03/05/20180305209209_JzKLWN.jpg)

          ---

          ### 开发阶段
          - 没有接口文档, 更没有接口, 等则耽误自己的工期
          - 不等则联调时的字段命名, 数据结构区别很大
          - 没有数据, 造数据困难又不优雅

          ---

          ### 联调阶段
          - 跨域, 改个响应头都不愿意
          - 接口出错, 参数沟通反反复复, 好好的前端工程师变成给后端测接口的打工人
          - 演示时没有数据或数据错乱, 导致界面效果极差还容易产生异常
          - 页面效果需要接口数据支持, 后端改起来麻烦, 去造业务又没走通
          - 之前好好的接口突然出错, 影响工作

          ---

          ## 跨域
          ![跨域](https://z3.ax1x.com/2021/08/01/Wzw3h4.png)

          ---


          ### 方案
          - 常见方案
            - nginx  <!-- .element: class="fragment" -->
            - 后端  <!-- .element: class="fragment" -->
            - devServer  <!-- .element: class="fragment" -->
            - 浏览器插件  <!-- .element: class="fragment" -->
            - 修改浏览器的安全设置  <!-- .element: class="fragment" -->
          - 我的方案 <!-- .element: class="fragment" -->
            - mockm  <!-- .element: class="fragment" -->
            ``` sh
            mm proxy=要跨的域
            ```

          <aside class="notes" data-markdown>
            - nginx
              - 需要上服务器操作
              - 危险系数高, 不小心 `rm -rf /` 就玩完
            - 后端
              - 很多人懒得搞, 甚至半天搞不定, 还觉得是前端或运维该做的
            - devServer
              - 仅 webpack 项目支持, 从浏览器中不易清楚数据来源, 必须了解 proxy 配置
            - 浏览器插件
              - 危险, 仅本地
            - 修改浏览器的安全设置
              - 危险, 仅本地
            - mockm
              - 简单且拥有配套的常用功能
          </aside>
          ---


          ### 使用方法

          ``` sh [1-2|4-5|7-8]
          # 安装
          npm i -g mockm

          # 代理
          mm proxy=要跨的域

          # 使用代理
          在开发环境使用 http://127.0.0.1:9000/...
          ```

          <aside class="notes" data-markdown>
            - cnpm i -g mockm
            - npm i -g mockm --registry=https://registry.npm.taobao.org

            - 简单的 nodejs 接口服务

            ``` js
              const http = require('http');
              const server = http.createServer((req, res) => {
                res.writeHead(200, {
                  'x-age': '18',
                });
                res.end('ok');
              });
              server.listen(8000, () => console.log(`启动成功 http://localhost:8000`));
            ```

            - 请求接口
            ``` js
            await fetch('http://localhost:8000/api/test').then(res => res.text())
            ```

          </aside>

          ---

          ### 跨域原理
          跨域是指浏览器对不同域下面的资源访问限制。

          <aside class="notes" data-markdown>
            - 不同域? 协议、主域名、子域、端口
            - 服务器被不存在跨域问题
            - 从响应头中约定如何跨域
          </aside>

          ---

          ## 数据或接口模拟

          ---

          ### 方案
          - 常见方案
            - 写死在代码中  <!-- .element: class="fragment" -->
            - 本地 json  <!-- .element: class="fragment" -->
            - 前端接口拦截  <!-- .element: class="fragment" -->
            - devServer  <!-- .element: class="fragment" -->
            - 自建接口模拟服务项目  <!-- .element: class="fragment" -->
            - 在线服务  <!-- .element: class="fragment" -->
            - 后端实现  <!-- .element: class="fragment" -->
          - 我的方案 <!-- .element: class="fragment" -->
            - mockm  <!-- .element: class="fragment" -->
            ``` sh
            mm
            ```

          <aside class="notes" data-markdown>
            - 写死在代码中, 例如直接在 vue 的 data 里写
              - 可维护性差
              - 容易出错
              - 大量代码冗余
            - 本地 json, 例如放置文件在 vue 的 public 目录中
              - 仅支持简单 get 文件
              - 不符合接口使用流程
            - 前端接口拦截, 例如 mockjs
              - 无法分离部署或共享
              - 受 mockjs 一些 bug 限制
              - 文件流拦截错误
              - 无法模拟 websoket
              - 浏览器看不到请求体
            - 自建接口模拟服务项目, 例如 json-server 或 koa 实现一个后端
              - 要处理的逻辑较多
              - 耗费太多额外精力
              - 最终停止维护
            - 在线服务, 例如 Easy-Mock
              - url通常不符项目定义, 是随机 url
              - 不能与项目共存, 不方便与项目同步更新或回退
              - 安全性和稳定性未知, 不知道线上平台能提供多久的服务
              - 不能离线使用, 某些平台支持自己部署, 但比较麻烦, 需要安装数据库或其他语言
                - https://zhuanlan.zhihu.com/p/139940483 安装nodejs、mogondb、redis
              - 不能进行复杂的接口模拟, 例如大文件下载, websoket, 自定义逻辑
              - 不能集成第三方工具, 例如要使用某个加密库处理响应数据
            - 后端实现
              - 不愿意
              - 没时间
              - 进度不易评估
            - mockm
              - 安装和使用简单
              - 是完整的 ndoejs 实现, 所以可以支持复杂的接口逻辑和功能
              - 拥有配套的常用功能, 例如响应拦截和修改, 报文记录和重放, 外网映射
          </aside>

          ---

          ### 使用方法
          自带接口服务

          ``` txt
          # 查看所有支持的接口
          # start http://127.0.0.1:9000/

          # 访问某个接口
          # start http://127.0.0.1:9000/get
          ```

          <aside class="notes" data-markdown>
            - 演示和讲解支持的接口
            - 来自 httpbin
          </aside>
          ---

          ### 使用方法
          通过配置文件自定义接口

          ``` js
          module.exports = {
            api: {
              // 当为基本数据类型时, 直接返回数据
              '/api/1': {msg: `ok`},

              // 一个 websocket 接口, 会发送收到的消息
              'ws /api/4' (ws, req) {
                ws.on('message', (msg) => ws.send(msg))
              }

              // 一个下载文件的接口
              '/file' (req, res) {
                res.download(`文件路径`, `文件名`)
              },

              // 获取动态的接口路径的参数 code
              '/status/:code' (req, res) {
                res.json({statusCode: req.params.code})
              },
            },
          }
          ```

          <aside class="notes" data-markdown>
            - 实时生效
            - 可移值
          </aside>
          ---

          ### 使用方法
          通过界面自定义接口
          ![通过界面自定义接口](https://z3.ax1x.com/2021/05/18/gfuHTU.gif) <!-- .element: width="600" height="400" -->

          <aside class="notes" data-markdown>
            - 自动翻译
            - 实时生效
            - 可协作编辑和移值
            - 自定义模式
            - 数据预览
            - 在线尝试
            - 启用禁用
          </aside>

          ---

          ## 接口联调
          - 参数沟通
          - 请求回放
          - 响应修改
          - 切换真实接口, 模拟接口
          - 服务重放模式

          <aside class="notes" data-markdown>
            - 参数沟通
            - 请求回放
            - 响应修改
            - 切换真实接口, 模拟接口
            - 服务重放模式
          </aside>

          ---

          ## 项目实战
            - 初始化
            - 接口和数据模拟
            - 静态文件
            - 外网映射
            - 线上部署
            - 项目分享

          <aside class="notes" data-markdown>
            - 初始化到
              - package.json
            - 接口和数据模拟
            - 静态文件
            - 外网映射
            - 线上部署
            - 项目分享
          </aside>

        </textarea>
      </section>
    </div>
  </div>

  <script src="https://cdn.jsdelivr.net/gh/hakimel/reveal.js@4.1.2/dist/reveal.js"></script>
  <script src="https://cdn.jsdelivr.net/gh/hakimel/reveal.js@4.1.2/plugin/notes/notes.js"></script>
  <script src="https://cdn.jsdelivr.net/gh/hakimel/reveal.js@4.1.2/plugin/markdown/markdown.js"></script>
  <script src="https://cdn.jsdelivr.net/gh/hakimel/reveal.js@4.1.2/plugin/highlight/highlight.js"></script>
  <script src="https://cdn.jsdelivr.net/gh/hakimel/reveal.js@4.1.2/plugin/zoom/zoom.js"></script>
  <script>
    // More info about initialization & config:
    // - https://revealjs.com/initialization/
    // - https://revealjs.com/config/
    Reveal.initialize({
      hash: true,
      markdown: {
        breaks: true,
      },
      // Learn about plugins: https://revealjs.com/plugins/
      plugins: [RevealMarkdown, RevealHighlight, RevealNotes, RevealZoom]
    });
  </script>
</body>

</html>
